#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sqlite3.h>
#include "colors.h"

#define QOVERVIEW 'o'       // default case - see get_filename()
#define QRPLAYER 'p'        // ?player=
#define QMLEADERBOARD 'm'   // ?map=
#define QFASTEST 'f'

static inline char *get_filename(char * const c) {
    char *qout = "queries/mranks.sql"; 
    if (c != NULL) {
        switch(*c) {
            default:
                *c = QOVERVIEW;
                break;
            case QMLEADERBOARD:
                qout = "queries/mleaderboard-ojoin.sql";
                break;
            case QRPLAYER:
                qout = "queries/rplayers.sql";
                break;
            case QFASTEST:
                qout = "queries/fastest-players.sql";
                break;
        }
    }
    return qout;
}

static inline void print_tblheader(const char *c) {
    char *labels;
    switch (*c) {
        default:
            labels = "<TABLE class='leaderboard'>\
             <TH class='tablename' COLSPAN='4'> <H3><BR>Map List</H3> </TH>\
             <TR>\
             <TH class='columnname'>Name</TH>\
             <TH class='columnname'>Records</TH>\
             <TH class='columnname'>Best Time</TH>\
             <TH class='columnname'>Held By</TH>\
             </TR>";
            break;
        case QMLEADERBOARD:
            labels = "<TABLE class='leaderboard'>\
                     <TH class='tablename' COLSPAN='3'><H3><BR>Leaderboard</H3></TH>\
                     <TR>\
                     <TH class='columnname'>Rank</TH>\
                     <TH class='columnname'>Name</TH>\
                     <TH class='columnname'>Time</TH>\
                     </TR>";
            break;
        case QRPLAYER:
            labels = "<TABLE class='leaderboard'>\
                 <TH class='tablename' COLSPAN='3'> <H3><BR>Ranks</H3> </TH>\
                 <TR>\
                 <TH class='columnname'>Name</TH>\
                 <TH class='columnname'>Map</TH>\
                 <TH class='columnname'>Rank</TH>\
                 </TR>";
            break;
        case QFASTEST:
            labels = "<table class='leaderboard'>\
             <th class='tablename' COLSPAN='4'> <H3><BR>Map List</H3> </th>\
             <tr>\
             <th class='columnname'>Name</th>\
             <th class='columnname'>Records</th>\
             <th class='columnname'>Highest Velocity (qu/s)</th>\
             <th class='columnname'>Held By</th>\
             </tr>";
    }
    printf("%s", labels);
}

// input is an integer as a string.
// the time is in centiseconds.
// this means the longest XDF run this can support is
// 21,474,836.47 seconds
// someone would need to idle in-game for 248 days
static void print_time(const unsigned char *strcs) {
    const unsigned long num = strtoul((const char*)strcs, NULL, 10);
    unsigned long s = num/100;
    const unsigned long cs  = num % 100;
    const unsigned long min = s/60;
    s = s % 60;
    if (min > 0) {
        printf("<TD>%u:%.2u.%.2u</TD>", min, s, cs);
    } else {
        printf("<TD>%u.%.2u</TD>", s, cs);
    }
}

// display and format the results of the query.
static void qresult(sqlite3_stmt * const sp, const char *c) {
#define ISPLAYERNAME(x, y) (y == 1 && *x == QMLEADERBOARD) || \
                           (y == 3 && *x == QOVERVIEW)|| \
                           (y == 0 && *x == QRPLAYER) || \
                           (y == 3 && *x == QFASTEST)
#define ISMAPNAME(x, y) (y == 0 && *x == QOVERVIEW) ||\
                        (y == 1 && *x == QRPLAYER) || \
                        (y == 0 && *x == QFASTEST)
    int e;
    unsigned int i;
    const unsigned int cc  = sqlite3_column_count(sp);
    print_tblheader(c);
    while ((e = sqlite3_step(sp)) == SQLITE_ROW) {
        printf("<TR>");
        for (i = 0; i < cc; ++i) {
            unsigned const char * const field = sqlite3_column_text(sp, i);
            if (ISPLAYERNAME(c, i)) { 
                printf("<TD>");
                print_plname(field);
                printf("</TD>");
            } else if (ISMAPNAME(c, i)) {
#ifdef STATICGEN
                printf("<TD><a href='./maps/%s.html'>%s</a></TD>", field, field);
#else
                printf("<TD><a href='/cgi/cts?map=%s'>%s</a></TD>", field, field);
#endif
            } else if (i == 2 && (*c == QMLEADERBOARD || *c == QOVERVIEW)) {
                print_time(field);
            } else if (i == 2 && *c == QFASTEST) { // velocity
                printf("<TD>%.2f</TD>", atof(field) );
            } else {
                printf("<TD>%s</TD>", field);
            }
        }
        printf("</TR>\n");
    }
    printf("</TABLE>");
}

// const char 'sql' is never NULL.
// const char 'str' may be NULL, however it is only when 
// the contents of mranks.sql is in const char 'sql'.
static bool executequery(const char *sql, const char *str) {
    const char *p = (str != NULL) ? strchr(str, '=') + 1 : NULL;
    const char qc = (str != NULL) ? str[0] : QOVERVIEW;
    sqlite3    *db;
    int con = sqlite3_open("db/cts.db", &db);
    if (con != SQLITE_OK) {
        fprintf(stderr, "Can not open database: %s\n", sqlite3_errmsg(db));
        return false;
    }
    sqlite3_stmt *s;
    // prepare sql to s, reading until null character.
    con = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
    if (con != SQLITE_OK) {
        fprintf(stderr, "Could not prepare SQL statement: %s\n\n%s", sqlite3_errmsg(db), sql);
        return false;
    }
    // bind parameter with a string length that is the # of bytes to null char.
    con = sqlite3_bind_text(s, 1, p, -1, NULL);
    qresult(s, &qc);
    sqlite3_finalize(s);
    sqlite3_close(db);
    return true;
}

// both allocates and frees memory used up by the string..
// containing the data from query files.
void getquery(char * const qs) {
    char *qf = NULL;
    char *fname = get_filename(qs);
    if (fname != NULL) { 
        FILE *f = fopen(fname, "r");
        if (f != NULL) {
            // change from fseek/SEEK_END when sql queries are >2 GB in size.
            fseek(f, 0, SEEK_END);         // go to the end of file
            unsigned int size = ftell(f);
            qf = calloc(size, sizeof(char));
            if (qf != NULL) {
                fseek(f, 0, SEEK_SET);     // go to the start of file
                fread(qf, sizeof(char), size, f);
            }
        }
        fclose(f);
    }
    executequery(qf, qs);
    free(qf);
}

